using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Windows.Forms;
using igp.net.DataModel;

namespace igp.net.DataModel
{
    /// <summary>
    /// This class represents a single project.
    /// </summary>
    public class IgpProject
    {
        private string buffer;

        private int FunctionCount;

        private int IntegralCount;

        private int LabelCount;

        private int PointSetCount;

        private int PolynomialCount;

        private IgpProject()
        {
            // Expression.SetConstant("x", 0);
            // Expression.SetConstant("n", 1);
            clearProject();
        }

        /// <summary>
        /// Gives access to the only exising instance of this class.
        /// </summary>
        public static IgpProject Singleton { get; private set; }

        /// <summary>
        /// Gives access to the View object associated with the project.
        /// </summary>
        public IgpView View { get; private set; }

        /// <summary>
        /// Gives access to the graphics object associated with the project. 
        /// It gives the possibility to draw additional stuff on the graph.
        /// </summary>
        public Graphics Graph { get; private set; }

        /// <summary>
        /// Gives access to the Bitmap containing the picture of the project.
        /// </summary>
        public Bitmap Bitmap { get; private set; }

        /// <summary>
        /// Gives access to the Items contained in the project.
        /// </summary>
        public List<IgpItem> Items { get; private set; }

        /// <summary>
        /// Gives possibility to get/set currently selected pointset.
        /// </summary>
        public igpPointSet CurrentPointset { get; set; }

        /// <summary>
        /// Gives possibility to get/set currently selected polynomial.
        /// </summary>
        public igpPolynomial CurrentPolynomial { get; set; }

        /// <summary>
        /// Gives possibility to get/set currently selected label.
        /// </summary>
        public IgpLabel CurrentLabel { get; set; }

        /// <summary>
        /// Gives possibility to get/set currently selected function.
        /// </summary>
        public IgpFunction CurrentFunction { get; set; }

        /// <summary>
        /// Gives possibility to get/set currently selected integral.
        /// </summary>
        public igpIntegral CurrentIntegral { get; set; }

        /// <summary>
        /// Gives access to the cursor that can be used to point on something of the screen.
        /// The curor is used to show the value of the function.
        /// </summary>
        public IgpEvaluationCursor Cursor { get; private set; }

        /// <summary>
        /// Create an instance of this class. You can do that only once. 
        /// If an instance already exists an exception will be thrown.
        /// </summary>
        /// <returns>The instance that has just been created.</returns>
        public static IgpProject createProject()
        {
            if (Singleton != null)
            {
                throw new Exception("An instance of this class already exists.");
            }

            igpSettings.create();

            Singleton = new IgpProject();
            return Singleton;
        }

        /// <summary>
        /// Get item by index.
        /// </summary>
        /// <param name="index">Item index</param>
        /// <returns>Item if found, otherwise null</returns>
        public IgpItem GetItem(int index)
        {
            IEnumerator item = Items.GetEnumerator();
            item.Reset();
            int temp = -1;
            if (index >= Items.Count || index < 0) throw new Exception("IgpItem: index out of range");
            while (temp < index)
            {
                temp++;
                item.MoveNext();
            }
            return (IgpItem) item.Current;
        }

        /// <summary>
        /// Get item by name.
        /// </summary>
        /// <param name="nam">Item name</param>
        /// <returns>Item if found, otherwise null</returns>
        public IgpItem GetItem(string nam)
        {
            IEnumerator item = Items.GetEnumerator();
            item.Reset();
            while (item.MoveNext())
            {
                if (((IgpItem) item.Current).ItemName == nam)
                {
                    return (IgpItem) item.Current;
                }
            }

            return null;
        }

        /// <summary>
        /// Get item by index. Limit only to items of specified type.
        /// </summary>
        /// <param name="index">Item index</param>
        /// <param name="type">Item type</param>
        /// <returns>Item if found, otherwise null</returns>
        public IgpItem GetItem(int index, IgpItemType type)
        {
            if (index >= Items.Count) throw new Exception("igpItems: index out of range");
            IEnumerator item = Items.GetEnumerator();
            item.Reset();
            int temp = -1;

            while (temp < index)
            {
                if (item.MoveNext() == false) throw new Exception("IgpItem: index out of range");
                if (((IgpItem) item.Current).TypeID == type) temp++;
            }

            return (IgpItem) item.Current;
        }

        /// <summary>
        /// Get function by index. Function includes polynomials and polynomials.
        /// </summary>
        /// <param name="index">Function index</param>
        /// <returns>Item if found, otherwise null</returns>
        public IgpFunctionBase GetFunction(int index)
        {
            if (index >= Items.Count) throw new Exception("igpItems: index out of range");
            IEnumerator item = Items.GetEnumerator();
            item.Reset();
            int temp = -1;

            while (temp < index)
            {
                if (item.MoveNext() == false) throw new Exception("IgpItem: index out of range");
                if (((IgpItem) item.Current).TypeID == IgpItemType.Function ||
                    ((IgpItem) item.Current).TypeID == IgpItemType.Polynomial) temp++;
            }

            return (IgpFunctionBase) item.Current;
        }

        /// <summary>
        /// Forces project to Render;
        /// </summary>
        public void Draw()
        {
            View.Draw(Graph);

            IEnumerator item = Items.GetEnumerator();
            item.Reset();
            while (item.MoveNext())
            {
                if (((IgpItem) (item.Current)).IsVisible) ((IgpItem) (item.Current)).Draw(Graph);
            }

            View.Legend.Draw(Graph);
        }

        /// <summary>
        /// Stores view data to a buffer.
        /// </summary>
        public void BufferStore()
        {
            TextWriter tw = new StringWriter();
            View.SaveData(tw);
            buffer = tw.ToString();
            tw.Close();
        }

        /// <summary>
        /// Restores view data from a buffer.
        /// </summary>
        public void BufferRestore()
        {
            TextReader tr = new StringReader(buffer);
            View.ReadData(tr);
            View.Axes.ReadData(tr);
            View.Legend.ReadData(tr);
            tr.Close();
        }

        /// <summary>
        /// Checks whether the buffer contains any data or not.
        /// </summary>
        /// <returns>false if buffer empty, true otherwise</returns>
        public bool BufferReady()
        {
            return !string.IsNullOrEmpty(buffer);
        }

        /// <summary>
        /// Sets project settings to defaults.
        /// </summary>
        public void clearProject()
        {
            buffer = "";
            Items = new List<IgpItem>();
            View = new IgpView();
            Bitmap = new Bitmap(View.Width, View.Height);
            Graph = Graphics.FromImage(Bitmap);
            Cursor = new IgpEvaluationCursor();

            Graph.SmoothingMode = igpSettings.Singleton.DrawMode;
            PointSetCount = 0;
            FunctionCount = 0;
            PolynomialCount = 0;
            LabelCount = 0;
            IntegralCount = 0;
            CurrentFunction = null;
            CurrentLabel = null;
            CurrentPointset = null;
            CurrentPolynomial = null;
            CurrentIntegral = null;
        }

        /// <summary>
        /// Forces graphics reinitialization.
        /// </summary>
        public void ReinitializeGraphics()
        {
            Bitmap = new Bitmap(View.Width, View.Height);
            Graph = Graphics.FromImage(Bitmap);
            Graph.SmoothingMode = igpSettings.Singleton.DrawMode;
        }

        /// <summary>
        /// Checks whether current items are still on the items list. If not references are set to null.
        /// </summary>
        public void VerifyList()
        {
            if (!Items.Contains(CurrentFunction)) CurrentFunction = null;
            if (!Items.Contains(CurrentIntegral)) CurrentIntegral = null;
            if (!Items.Contains(CurrentLabel)) CurrentLabel = null;
            if (!Items.Contains(CurrentPointset)) CurrentPointset = null;
            if (!Items.Contains(CurrentPolynomial)) CurrentPolynomial = null;
        }

        /// <summary>
        /// Add new pointset.
        /// </summary>
        /// <returns>Added pointset</returns>
        public igpPointSet AddPointSet()
        {
            var item = new igpPointSet {ItemID = (++PointSetCount)};
            item.ItemName += item.ItemID;
            Singleton.Items.Add(item);

            return item;
        }

        /// <summary>
        /// Add new polynomial.
        /// </summary>
        /// <returns>Added polynomial</returns>
        public igpPolynomial AddPolynomial()
        {
            var item = new igpPolynomial {ItemID = (++PolynomialCount)};
            item.ItemName += item.ItemID;
            Singleton.Items.Add(item);

            return item;
        }

        /// <summary>
        /// Add new label
        /// </summary>
        /// <returns>Added label</returns>
        public IgpLabel AddLabel()
        {
            var item = new IgpLabel {ItemID = (++LabelCount)};
            item.ItemName += item.ItemID;
            Singleton.Items.Add(item);

            return item;
        }

        /// <summary>
        /// Add new function
        /// </summary>
        /// <returns>Added function</returns>
        public IgpFunction AddFunction()
        {
            var item = new IgpFunction {ItemID = (++FunctionCount)};
            item.ItemName += item.ItemID;

            Singleton.Items.Add(item);

            if (FormMain.Singleton.EditorWindow.GetActivePage() == 1)
            {
                FormMain.Singleton.EditorWindow.ReadPointSetProperties();
            }

            if (FormMain.Singleton.EditorWindow.GetActivePage() == 5)
            {
                FormMain.Singleton.EditorWindow.ReadIntegralProperities();
            }

            return item;
        }

        /// <summary>
        /// Add new integral
        /// </summary>
        /// <returns>Added integral</returns>
        public igpIntegral AddIntegral()
        {
            int cnt = GetCount(IgpItemType.Polynomial) + GetCount(IgpItemType.Function);

            if (cnt == 0) throw new Exception("error: to add an integral you need a function first");

            var item = new igpIntegral {ItemID = (++IntegralCount)};
            item.ItemName += item.ItemID;
            Singleton.Items.Add(item);

            return item;
        }

        /// <summary>
        /// This method updates polynomials, which are associated with a pointset.
        /// </summary>
        public void UpdatePolynomials()
        {
            IEnumerator item = Items.GetEnumerator();
            item.Reset();
            while (item.MoveNext())
            {
                if (((IgpItem) item.Current).TypeID == IgpItemType.Polynomial)
                {
                    if (((igpPolynomial) item.Current).TrendlinePoints != null)
                    {
                        ((igpPolynomial) item.Current).TrendlineUpdate();
                    }
                }
            }
        }

        /// <summary>
        /// This method corrects some parameters referring to the rendering engine.
        /// It should be invoked when image dimensions are changed.
        /// </summary>
        public void UpdateResolutions()
        {
            View.UpdateDistances();
            Bitmap = new Bitmap(View.Width, View.Height);
            Graph = Graphics.FromImage(Bitmap);
            Graph.SmoothingMode = igpSettings.Singleton.DrawMode;
        }

        /// <summary>
        /// Counts items in the project. Same as project.Items.Count;
        /// </summary>
        /// <returns>Items count</returns>
        public int GetCount()
        {
            return Items.Count;
        }

        /// <summary>
        /// Counts items that are visible on the legend.
        /// </summary>
        /// <returns>Items count</returns>
        public int GetLegendCount()
        {
            int count = 0;
            foreach (IgpItem item in Items)
            {
                if (item.VisibleOnLegend &&
                    (item.TypeID == IgpItemType.Polynomial ||
                     item.TypeID == IgpItemType.PointSet ||
                     item.TypeID == IgpItemType.Function))
                    count++;
            }


            return count;
        }

        /// <summary>
        /// Counts visible items.
        /// </summary>
        /// <returns>Items count</returns>
        public int GetVisibleCount()
        {
            IEnumerator item = Items.GetEnumerator();
            item.Reset();
            int count = 0;
            while (item.MoveNext())
            {
                if (((IgpItem) item.Current).IsVisible) count++;
            }
            return count;
        }

        /// <summary>
        /// Counts selected items.
        /// </summary>
        /// <returns>Items count</returns>
        public int GetSelectedCount()
        {
            IEnumerator item = Items.GetEnumerator();
            item.Reset();
            int count = 0;
            while (item.MoveNext())
            {
                if (((IgpItem) item.Current).IsSelected) count++;
            }
            return count;
        }

        /// <summary>
        /// Counts items of specified type.
        /// </summary>
        /// <param name="type">Item type to be looked for</param>
        /// <returns>Items count</returns>
        public int GetCount(IgpItemType type)
        {
            IEnumerator item = Items.GetEnumerator();
            item.Reset();
            int count = 0;
            while (item.MoveNext())
            {
                if (((IgpItem) item.Current).TypeID == type) count++;
            }
            return count;
        }

        /// <summary>
        /// Load settings from file.
        /// </summary>
        /// <param name="FileName">Settings file path</param>
        public void LoadSettings(String FileName)
        {
            var sr = new StreamReader(FileName);
            String Line;
            
            while ((Line = sr.ReadLine()) != null)
            {
                char[] sep = {'='};
                String[] cmd = Line.Split(sep, 2);
                ParseSettingsLine(cmd[0], cmd[1]);
            }
            
            sr.Close();
        }

        private void ParseSettingsLine(String Command, String Argument)
        {
            FormMain mainform = FormMain.Singleton;

            if (Command.StartsWith("EnableVisualStyles"))
            {
                if (Argument.EndsWith("True"))
                {
                    igpSettings.Singleton.EnableVisualStyles = true;
                    Application.EnableVisualStyles();
                }
            }

            if (Command.StartsWith("MainWindowX"))
            {
                mainform.Left = int.Parse(Argument);
            }
            if (Command.StartsWith("MainWindowY"))
            {
                mainform.Top = int.Parse(Argument);
            }
            if (Command.StartsWith("MainWindowWidth"))
            {
                mainform.Width = int.Parse(Argument);
            }
            if (Command.StartsWith("MainWindowHeight"))
            {
                mainform.Height = int.Parse(Argument);
            }

            if (Command.StartsWith("ShowPickerWindow"))
            {
                igpSettings.Singleton.ShowPicker = bool.Parse(Argument);
                //if (igpSettings.Singleton.ShowPicker) mainform.PickerWindow.Show();
            }
            if (Command.StartsWith("ShowEditorWindow"))
            {
                igpSettings.Singleton.ShowEditor = bool.Parse(Argument);
                //if (igpSettings.Singleton.ShowEditor) mainform.EditorWindow.Show();
            }
            if (Command.StartsWith("PickerWindowX"))
            {
                igpSettings.Singleton.PickerWindowLeft = int.Parse(Argument);
            }
            if (Command.StartsWith("PickerWindowY"))
            {
                igpSettings.Singleton.PickerWindowTop = int.Parse(Argument);
            }
            if (Command.StartsWith("PickerWindowWidth"))
            {
                igpSettings.Singleton.PickerWindowWidth = int.Parse(Argument);
            }
            if (Command.StartsWith("PickerWindowHeight"))
            {
                igpSettings.Singleton.PickerWindowHeight = int.Parse(Argument);
            }
            if (Command.StartsWith("EditorWindowX"))
            {
                igpSettings.Singleton.EditorWindowLeft = int.Parse(Argument);
            }
            if (Command.StartsWith("EditorWindowY"))
            {
                igpSettings.Singleton.EditorWindowTop = int.Parse(Argument);
            }
            if (Command.StartsWith("EditorWindowHeight"))
            {
                igpSettings.Singleton.EditorWindowHeight = int.Parse(Argument);
            }
            if (Command.StartsWith("Item"))
            {
                Argument = Argument.Substring(1);
                mainform.AddRecentDocument(Argument);
            }
            if (Command.StartsWith("ViewBorder"))
            {
                if (Argument.EndsWith("True")) igpSettings.Singleton.ViewBorder = true;
            }
            if (Command.StartsWith("DrawMode"))
            {
                igpSettings.Singleton.DrawMode = (SmoothingMode) int.Parse(Argument);
                Graph.SmoothingMode = igpSettings.Singleton.DrawMode;
            }
            if (Command.StartsWith("DrawingDetails"))
            {
                igpSettings.Singleton.DrawingDetails = double.Parse(Argument);
            }
        }

        /// <summary>
        /// Saves igp.net settings to a file.
        /// </summary>
        /// <param name="FileName">File path</param>
        public void SaveSettings(String FileName)
        {
            FormMain mainform = FormMain.Singleton;

            var sw = new StreamWriter(FileName);

            sw.WriteLine("EnableVisualStyles = " + igpSettings.Singleton.EnableVisualStyles);
            sw.WriteLine("MainWindowX = " + mainform.Left);
            sw.WriteLine("MainWindowY = " + mainform.Top);
            sw.WriteLine("MainWindowWidth = " + mainform.Width);
            sw.WriteLine("MainWindowHeight = " + mainform.Height);
            sw.WriteLine("ShowPickerWindow = " + igpSettings.Singleton.ShowPicker);
            sw.WriteLine("ShowEditorWindow = " + igpSettings.Singleton.ShowEditor);
            sw.WriteLine("PickerWindowX = " + mainform.PickerWindow.Left);
            sw.WriteLine("PickerWindowY = " + mainform.PickerWindow.Top);
            sw.WriteLine("PickerWindowWidth = " + mainform.PickerWindow.Width);
            sw.WriteLine("PickerWindowHeight = " + mainform.PickerWindow.Height);
            sw.WriteLine("EditorWindowX = " + mainform.EditorWindow.Left);
            sw.WriteLine("EditorWindowY = " + mainform.EditorWindow.Top);
            sw.WriteLine("EditorWindowHeight = " + mainform.EditorWindow.Height);
            sw.WriteLine("ViewBorder = " + igpSettings.Singleton.ViewBorder);
            sw.WriteLine("DrawingDetails = " + igpSettings.Singleton.DrawingDetails);
            sw.WriteLine("DrawMode = " + ((int) igpSettings.Singleton.DrawMode).ToString().ToLower());

            foreach (string a in mainform.RecentDocuments)
            {
                sw.WriteLine("Item = " + a);
            }

            sw.Close();
        }

        /// <summary>
        /// Save project to file.
        /// </summary>
        /// <param name="FileName">File path</param>
        public void SaveData(String FileName)
        {
            var sw = new StreamWriter(FileName, false);
            sw.WriteLine("<info>");
            sw.WriteLine("format version = 2.0");
            sw.WriteLine("</info>");

            View.SaveData(sw);

            IEnumerator item = Items.GetEnumerator();
            item.Reset();
            while (item.MoveNext())
            {
                ((IgpItem) item.Current).SaveData(sw);
            }

            sw.Close();
        }

        /// <summary>
        /// Save project to file (include only visible items).
        /// </summary>
        /// <param name="FileName">File path</param>
        public void SaveVisibleData(String FileName)
        {
            var sw = new StreamWriter(FileName, false);
            sw.WriteLine("<info>");
            sw.WriteLine("format version = 2.0");
            sw.WriteLine("</info>");

            View.SaveData(sw);

            IEnumerator item = Items.GetEnumerator();
            item.Reset();
            while (item.MoveNext())
            {
                if (((IgpItem) item.Current).IsVisible)
                {
                    ((IgpItem) item.Current).SaveData(sw);
                }
            }

            sw.Close();
        }

        /// <summary>
        /// Load project from a file.
        /// </summary>
        /// <param name="FileName">File path</param>
        public void ReadData(String FileName)
        {
            var sr = new StreamReader(FileName);
            String Line;

            while ((Line = sr.ReadLine()) != null)
            {
                if (Line.StartsWith("<info>"))
                {
                }

                if (Line.StartsWith("<View>"))
                {
                    View.ReadData(sr);
                }

                if (Line.StartsWith("<legend>"))
                {
                    View.Legend.ReadData(sr);
                }

                if (Line.StartsWith("<axes>"))
                {
                    View.Axes.ReadData(sr);
                }

                if (Line.StartsWith("<pointset>"))
                {
                    igpPointSet pt = AddPointSet();
                    pt.ReadData(sr);
                }

                if (Line.StartsWith("<polynomial>"))
                {
                    igpPolynomial po = AddPolynomial();
                    po.ReadData(sr);
                }

                if (Line.StartsWith("<label>"))
                {
                    IgpLabel lb = AddLabel();
                    lb.ReadData(sr);
                }

                if (Line.StartsWith("<function>"))
                {
                    IgpFunction lb = AddFunction();
                    lb.ReadData(sr);
                }

                if (Line.StartsWith("<integral>"))
                {
                    igpIntegral lb = AddIntegral();
                    lb.ReadData(sr);
                }
            }

            IEnumerator item = Items.GetEnumerator();
            item.Reset();
            while (item.MoveNext())
            {
                if (((IgpItem) item.Current).TypeID == IgpItemType.Polynomial)
                {
                    if (((igpPolynomial) item.Current).TrendlineName != "")
                    {
                        ((igpPolynomial) item.Current).TrendlinePoints = (igpPointSet)
                                                                         Singleton.GetItem(
                                                                             ((igpPolynomial) item.Current).
                                                                                 TrendlineName);
                        ((igpPolynomial) item.Current).TrendlineName = "";
                    }
                }

                if (((IgpItem) item.Current).TypeID == IgpItemType.Integral)
                {
                    var integral = ((igpIntegral) item.Current);
                    if (integral.TopFunctionName != "")
                    {
                        integral.TopFunctionBase = (IgpFunctionBase)
                                                   Singleton.GetItem(integral.TopFunctionName);
                        integral.TopFunctionName = "";
                    }

                    if (integral.BottomFunctionName != "")
                    {
                        integral.BottomFunctionBase = (IgpFunctionBase)
                                                      Singleton.GetItem(integral.BottomFunctionName);
                        integral.BottomFunctionName = "";
                    }
                }
            }

            Singleton.UpdatePolynomials();

            sr.Close();
        }

        ///<summary>
        /// Adapt view ranges to fit a specified pointset.
        ///</summary>
        ///<param name="pointset"></param>
        public void FitPointSet(igpPointSet pointset)
        {
            IEnumerator item = pointset.Points.GetEnumerator();
            item.Reset();
            item.MoveNext();

            double minx = ((IgpPoint)item.Current).X;
            double maxx = ((IgpPoint)item.Current).X;
            double miny = ((IgpPoint)item.Current).Y;
            double maxy = ((IgpPoint)item.Current).Y;

            while (item.MoveNext())
            {
                if (((IgpPoint)item.Current).X < minx) minx = ((IgpPoint)item.Current).X;
                if (((IgpPoint)item.Current).X > maxx) maxx = ((IgpPoint)item.Current).X;
                if (((IgpPoint)item.Current).Y < miny) miny = ((IgpPoint)item.Current).Y;
                if (((IgpPoint)item.Current).Y > maxy) maxy = ((IgpPoint)item.Current).Y;
            }

            if (!View.LogScaleX)
            {
                View.MinX = minx;
                View.MaxX = maxx;
                View.MinX -= (maxx - minx) / 10;
                View.MaxX += (maxx - minx) / 10;
            }
            else
            {
                View.LogMinX = Math.Pow(10, (10 * Math.Log10(minx) - (10 * Math.Log10(maxx) - 10 * Math.Log10(minx)) / 10) / 10);
                View.LogMaxX = Math.Pow(10, (10 * Math.Log10(maxx) + (10 * Math.Log10(maxx) - 10 * Math.Log10(minx)) / 10) / 10);
            }

            if (!View.LogScaleY)
            {
                View.MinY = miny;
                View.MaxY = maxy;
                View.MinY -= (maxy - miny) / 10;
                View.MaxY += (maxy - miny) / 10;
            }
            else
            {
                View.LogMinY = Math.Pow(10, (10 * Math.Log10(miny) - (10 * Math.Log10(maxy) - 10 * Math.Log10(miny)) / 10) / 10);
                View.LogMaxY = Math.Pow(10, (10 * Math.Log10(maxy) + (10 * Math.Log10(maxy) - 10 * Math.Log10(miny)) / 10) / 10);
            }

            View.UpdateDistances();
        }

        ///<summary>
        /// Adds a new point set with values that correspond to the function specified. The function is sampled in uniformly distributed points
        /// based on their domain limits and specified point count.
        ///</summary>
        ///<param name="poly">function to be sampled</param>
        ///<param name="count">number of desired points</param>
        ///<exception cref="Exception"></exception>
        public void SampleFunction(IgpFunctionBase poly, int count)
        {
            if (poly.DomainLower == poly.DomainHigher)
            {
                throw new Exception("Cannot create a pointset from unlimited function");
            }

            igpPointSet ptset = AddPointSet();
            ptset.ItemName = "PointSet (" + poly.ItemName + ")";

            double step = (poly.DomainHigher - poly.DomainLower) / (count - 1);

            for (int a = 0; a < count; a++)
            {
                double tx = poly.DomainLower + a * step;
                ptset.AddPoint(tx, poly.Value(tx));
            }
        }

        ///<summary>
        /// Adds interpolation polynomial based on points from a pointset.
        ///</summary>
        ///<param name="pointset"></param>
        ///<param name="mode"></param>
        ///<exception cref="Exception"></exception>
        public void AddInterpolation(igpPointSet pointset, IgpInterpolationMode mode)
        {
            if (pointset.Points.Count < 2) throw new Exception("This operation requires at least 2 points!");

            igpPolynomial item = AddPolynomial();

            item.TrendlineMode = mode;
            item.TrendlinePoints = pointset;
            

            double minx = 0;
            double maxx = 0;

            if (mode != IgpInterpolationMode.Linear)
            {

                IEnumerator iter = pointset.Points.GetEnumerator();
                iter.Reset();
                iter.MoveNext();
            
                minx = ((IgpPoint)iter.Current).X;
                maxx = ((IgpPoint)iter.Current).X;

                while (iter.MoveNext())
                {
                    if (((IgpPoint)iter.Current).X < minx) minx = ((IgpPoint)iter.Current).X;
                    if (((IgpPoint)iter.Current).X > maxx) maxx = ((IgpPoint)iter.Current).X;
                }
            }

            item.DomainLower = minx;
            item.DomainHigher = maxx;
            item.TrendlineUpdate();

            item.ItemName = string.Format("{1} ({0})", pointset.ItemName, mode);
        }

        ///<summary>
        /// Adds a tangent to a function in a specified location.
        ///</summary>
        ///<param name="function"></param>
        ///<param name="x"></param>
        public void AddTangent(IgpFunctionBase function, double x)
        {
            double df = function.Derivative(x);
            double f = function.Value(x);

            igpPolynomial polynomial = AddPolynomial();

            polynomial.AddMonomial(df, 1);
            polynomial.AddMonomial(f - df * x, 0);
            polynomial.LineColor = Color.Red;
            polynomial.ItemName = string.Format("Tangent({0})", function.ItemName);
            polynomial.DomainHigher = function.DomainHigher;
            polynomial.DomainLower = function.DomainLower;
        }
    }
}